/*
 * Segmentation-based user mode implementation
 * Copyright (c) 2001,2003 David H. Hovemeyer <daveho@cs.umd.edu>
 * $Revision: 1.23 $
 * 
 * This is free software.  You are permitted to use,
 * redistribute, and modify it as specified in the file "COPYING".
 */

#include <geekos/ktypes.h>
#include <geekos/kassert.h>
#include <geekos/defs.h>
#include <geekos/mem.h>
#include <geekos/string.h>
#include <geekos/malloc.h>
#include <geekos/int.h>
#include <geekos/gdt.h>
#include <geekos/segment.h>
#include <geekos/tss.h>
#include <geekos/kthread.h>
#include <geekos/argblock.h>
#include <geekos/user.h>
#include <geekos/errno.h>


/*
 * This is defined in lowlevel.asm.
 */
extern void Load_LDTR(ulong_t ldtSelector);


/* ----------------------------------------------------------------------
 * Variables
 * ---------------------------------------------------------------------- */

#define DEFAULT_USER_STACK_SIZE 8192


/* ----------------------------------------------------------------------
 * Private functions
 * ---------------------------------------------------------------------- */



/*
 * Create a new user context of given size
 */

/* TODO: Implement
static struct User_Context* Create_User_Context(ulong_t size)
*/


static bool Validate_User_Memory(struct User_Context* userContext,
    ulong_t userAddr, ulong_t bufSize)
{
    ulong_t avail;

    if (userAddr >= userContext->size)
        return false;

    avail = userContext->size - userAddr;
    if (bufSize > avail)
        return false;

    return true;
}

/* ----------------------------------------------------------------------
 * Public functions
 * ---------------------------------------------------------------------- */

/*
 * Destroy a User_Context object, including all memory
 * and other resources allocated within it.
 */
void Destroy_User_Context(struct User_Context* userContext)
{
    if (userContext->memory != 0)    /*Controlo que se haya liberado userContext->memory*/
        Free(userContext->memory);
        //Print("Error:  userContext->memory no pudo ser liberada\n");
    
    if (userContext->ldtDescriptor != 0) /*Controlo que se haya liberado userContext->ldtDescriptor*/
        Free_Segment_Descriptor(userContext->ldtDescriptor);
        //Print("Error: userContext->ldtDescriptor no pudo ser liberada\n");
    
    if (userContext != 0)    /*Controlo que se haya liberado userContext*/
        Free(userContext);
        //Print("Error: userContext no pudo ser liberada\n");  
}

/*
 * Load a user executable into memory by creating a User_Context
 * data structure.
 * Params:
 * exeFileData - a buffer containing the executable to load
 * exeFileLength - number of bytes in exeFileData
 * exeFormat - parsed ELF segment information describing how to
 *   load the executable's text and data segments, and the
 *   code entry point address
 * command - string containing the complete command to be executed:
 *   this should be used to create the argument block for the
 *   process
 * pUserContext - reference to the pointer where the User_Context
 *   should be stored
 *
 * Returns:
 *   0 if successful, or an error code (< 0) if unsuccessful
 */
int Load_User_Program(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat, const char *command, struct User_Context **pUserContext)
{   
    unsigned numArgs;   /*Numero de argumentos que tiene el main del ejecutable. La carga Get_Argument_Block_Size*/
    ulong_t argBlockSize;   /*Tamaño del bloque de argumentos del main del ejecutable. La carga Get_Argument_Block_Size*/
    int index = 0;  /*Indice*/
    ulong_t maxva = 0;  /*Almacena el maximo tamaño de los segmentos en memoria*/
    
    *pUserContext = Malloc(sizeof(struct User_Context));    /*Reserva memoria para la estructura User_Context que tenemos que cargar*/
    memset(*pUserContext, '\0',sizeof(struct User_Context));
    if ((*pUserContext) == NULL){
        goto falloFaltaMemoria;
    }
    
    Get_Argument_Block_Size(command, &numArgs, &argBlockSize);  /*Obtiene el tamaño de bloque para los argumentos y la cantidad de argumentos.*/
    
    /* Busca la maxima direccion virtual, lo hace para calcular el tamaño que ocupan los segmentos del ejecutable */
      for (index = 0; index < exeFormat->numSegments; ++index) {
        struct Exe_Segment *segment = &exeFormat->segmentList[index];
        ulong_t topva = segment->startAddress + segment->sizeInMemory; 
        
        if (topva > maxva)
          maxva = topva;
    }
    (*pUserContext)->size = Round_Up_To_Page(DEFAULT_USER_STACK_SIZE + argBlockSize + maxva); /*Calcula el tamaño total de la memoria del ejecutable*/

    /* Reserva memoria para el programa */
    (*pUserContext)->memory = Malloc((*pUserContext)->size);
     if ((*pUserContext)->memory == NULL){
        goto falloFaltaMemoria_memory;
    }
    memset((*pUserContext)->memory, '\0',(*pUserContext)->size);    /*Inicializa la memoria (en realidad pone todos 0)*/

    /* Carga los segmentos del ejecutable en la memoria */
    for (index = 0; index < exeFormat->numSegments; ++index) {
      struct Exe_Segment *segment = &exeFormat->segmentList[index];
      memcpy(((char*)(((ulong_t)(*pUserContext)->memory) + segment->startAddress)), exeFileData + segment->offsetInFile,segment->lengthInFile);
    }
        
    Format_Argument_Block(((char*)(((ulong_t)(*pUserContext)->memory) + maxva)), numArgs, maxva, command);  /*Arma y apila los argumentos del main*/

    (*pUserContext)->entryAddr = exeFormat->entryAddr;  /*Cargamos el punto desde donde debe empezar a ejecutarse el programa*/
    (*pUserContext)->argBlockAddr = maxva;  /*Porque el bloque de argumentos va a continuacion de los segmentos*/
    (*pUserContext)->stackPointerAddr = (*pUserContext)->size;   /*offset, es la ultima direccion de memory*/
    
    /* Aloca e inicializa los descriptores y selectores de codigo y dato */
    (*pUserContext)->ldtDescriptor= Allocate_Segment_Descriptor();  /*devuelve un puntero a la GDT*/
    Init_LDT_Descriptor((*pUserContext)->ldtDescriptor,(*pUserContext)->ldt,NUM_USER_LDT_ENTRIES);
    Init_Code_Segment_Descriptor((*pUserContext)->ldt,(unsigned long)(*pUserContext)->memory,((*pUserContext)->size/PAGE_SIZE),USER_PRIVILEGE);
    Init_Data_Segment_Descriptor((*pUserContext)->ldt + 1,(unsigned long)(*pUserContext)->memory,((*pUserContext)->size/PAGE_SIZE),USER_PRIVILEGE);
    (*pUserContext)->ldtSelector = Selector( KERNEL_PRIVILEGE, true, Get_Descriptor_Index( (*pUserContext)->ldtDescriptor )); 
    (*pUserContext)->csSelector = Selector( USER_PRIVILEGE, false, 0);   
	(*pUserContext)->dsSelector = Selector(USER_PRIVILEGE, false, 1);   
    (*pUserContext)->refCount = 0;  

    return 0;
    
falloFaltaMemoria_memory:
    if ((*pUserContext) != NULL){
        Free(*pUserContext);
    }
falloFaltaMemoria:
    return ENOMEM;  /*Error por fallo en Malloc*/
}

/*
 * Copy data from user memory into a kernel buffer.
 * Params:
 * destInKernel - address of kernel buffer
 * srcInUser - address of user buffer
 * bufSize - number of bytes to copy
 *
 * Returns:
 *   true if successful, false if user buffer is invalid (i.e.,
 *   doesn't correspond to memory the process has a right to
 *   access)
 */
bool Copy_From_User(void* destInKernel, ulong_t srcInUser, ulong_t bufSize)
{
    struct Kernel_Thread* kthread = Get_Current();
    if (Validate_User_Memory(kthread->userContext,srcInUser,bufSize)){  /*controlo que la cantidad de memoria no se vaya de rango*/
        memcpy(destInKernel,(void*)(srcInUser + (kthread->userContext)->memory) ,bufSize);
        return true;
    }
    return false; 
}

/*
 * Copy data from kernel memory into a user buffer.
 * Params:
 * destInUser - address of user buffer
 * srcInKernel - address of kernel buffer
 * bufSize - number of bytes to copy
 *
 * Returns:
 *   true if successful, false if user buffer is invalid (i.e.,
 *   doesn't correspond to memory the process has a right to
 *   access)
 */
bool Copy_To_User(ulong_t destInUser, void* srcInKernel, ulong_t bufSize)
{
    struct Kernel_Thread* kthread = Get_Current();
    if (Validate_User_Memory(kthread->userContext,destInUser, bufSize)){    /*controlo que la cantidad de memoria no se vaya de rango*/
       memcpy(destInUser + (kthread->userContext)->memory, srcInKernel, bufSize);
        return true;
    }
    return false;
}

/*
 * Switch to user address space belonging to given
 * User_Context object.
 * Params:
 * userContext - the User_Context
 */
void Switch_To_Address_Space(struct User_Context *userContext)
{
     Load_LDTR(userContext->ldtSelector);

}

